package evaluator

import (
	"dolphin/object"
	"fmt"
)

var builtins map[string]*object.Builtin

func initBuiltins() {
	builtins = map[string]*object.Builtin{
		"len": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 1 {
					return newError("wrong number of arguments. got=%d, want=1", len(args))
				}
				switch arg := args[0].(type) {
				case *object.Array:
					return &object.Integer{Value: int64(len(arg.Elements))}
				case *object.String:
					return &object.Integer{
						Value: int64(len(arg.Value)),
					}
				default:
					return newError("argument to `len` not supported, got %s", args[0].Type())
				}
			},
		},
		"first": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 1 {
					return newError("wrong number of arguments.got=%d,want=1", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("arguments to `first` must be ARRAY,got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				if len(arr.Elements) > 0 {
					return arr.Elements[0]
				}
				return NULL
			},
		},
		"last": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 1 {
					return newError("wrong number of arguments.got=%d,want=1", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("arguments to `first` must be ARRAY,got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				length := len(arr.Elements)
				if length > 0 {
					return arr.Elements[length-1]
				}
				return NULL
			},
		},
		"rest": {
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 1 {
					return newError("wrong number of arguments.got=%d,want=1", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("argument to `rest` must be ARRAY,got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				length := len(arr.Elements)
				if length > 0 {
					newElements := make([]object.Object, length-1, length-1)
					copy(newElements, arr.Elements[1:length])
					return &object.Array{Elements: newElements}
				}
				return NULL
			},
		},
		"push": {
			func(args ...object.Object) object.Object {
				if len(args) != 2 {
					return newError("wrong number of arguments.got=%d,want=2", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("argument to `push` must be ARRAY,got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				length := len(arr.Elements)
				newElements := make([]object.Object, length+1, length+1)
				copy(newElements, arr.Elements)
				newElements[length] = args[1]

				return &object.Array{Elements: newElements}
			},
		},
		"map": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 2 {
					return newError("wrong number of arguments. got=%d, want=2", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("first argument to `map` must be ARRAY, got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				fn, ok := args[1].(*object.Function)
				if !ok {
					return newError("second argument to `map` must be FUNCTION, got %s", args[1].Type())
				}
				var result []object.Object
				for _, elem := range arr.Elements {
					appliedResult := applyFunction(fn, []object.Object{elem})
					result = append(result, appliedResult)
				}
				return &object.Array{Elements: result}
			},
		},
		"reduce": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 3 {
					return newError("wrong number of arguments. got=%d, want=3", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("first argument to `reduce` must be ARRAY, got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				fn, ok := args[1].(*object.Function)
				if !ok {
					return newError("second argument to `reduce` must be FUNCTION, got %s", args[1].Type())
				}
				accumulator := args[2]
				for _, elem := range arr.Elements {
					accumulator = applyFunction(fn, []object.Object{accumulator, elem})
				}
				return accumulator
			},
		},
		"sum": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				if len(args) != 1 {
					return newError("wrong number of arguments. got=%d, want=1", len(args))
				}
				if args[0].Type() != object.ARRAY_OBJ {
					return newError("argument to `sum` must be ARRAY, got %s", args[0].Type())
				}
				arr := args[0].(*object.Array)
				sum := int64(0)
				for _, elem := range arr.Elements {
					if elem.Type() != object.INTEGER_OBJ {
						return newError("all elements in array passed to `sum` must be INTEGER, got %s", elem.Type())
					}
					sum += elem.(*object.Integer).Value
				}
				return &object.Integer{Value: sum}
			},
		},
		"puts": &object.Builtin{
			Fn: func(args ...object.Object) object.Object {
				for _, arg := range args {
					fmt.Println(arg.Inspect())
				}
				return NULL
			},
		},
	}
}

func init() {
	initBuiltins()
}
